package golf.stockinfo.buysellsimulation;

import golf.stockinfo.buysellsimulation.SimulationData.TransactionType;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Date;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeMap;

import org.joda.time.DateTime;
import org.joda.time.Days;

/**
 * This class is simulation on static cut-loss value.
 * @author golf
 *
 */
public class SimulateSET50AndTFEX_V2 {
	
	//The best SMAV is 9
	public static String weeklyFileName = "SET50-Weekly-19960617-20121112-MOV9.txt";
	public static String dailyFileName = "SET-Daily-19900102-20121102.txt";
	
	public static float INITIAL_CAPITAL = 500000;
	public static float TFEX_COST_PER_POINT = 1000;
	public static float CUT_LOSS_PERCENTAGE = -0.07f;
	
	public static int SMAV = 10;
	
	//The best cut loss is -13,000
	public static float CUT_LOSS_AMOUNT_TFEX = -5000.00f;
	public static Date START_DATE = Date.valueOf("2001-07-20");
	public static int SIMULATION_LENGTH_IN_YEAR = 10;
	
	/* 
	 * This WAIT_AFTER_CUT_LOSS if true means that cutloss and wait for the next opposite type of transaction
	 * Or of false mean that to immediately find another go-in point even if it's the same type of transaction
	 */
	public static boolean WAIT_AFTER_CUT_LOSS = true;
	
	private static TreeMap<DateTime, MarketData> weeklyData;
//	private TreeMap<DateTime, MarketData> dailyData;
	private ArrayList<SimulationData> simulationDataArray = new ArrayList<SimulationData>();
	private static DecimalFormat numberFormat = new DecimalFormat("###,###.00");
	private static DecimalFormat daysFormat = new DecimalFormat("###,###");

	/**
	 * 
	 * The best Moving average to be used is 9 weeks or SMAV(9)
	 * 
	 * @param args
	 */
	public static void main(String[] args) {

		SimulateSET50AndTFEX_V2 ths = new SimulateSET50AndTFEX_V2();
		ths.execute(true);
		
		System.out.println(ths.simulationDataArray.get(0).getStartDate() + ";" + ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate() + 
				";" + numberFormat.format(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance()));
		
		DateTime dt1 = new DateTime(ths.simulationDataArray.get(0).getStartDate());
		DateTime dt2 = new DateTime(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate());
		
		System.out.println("======================================================");
		System.out.println("Summary");
		System.out.println("======================================================");
		System.out.println("Investment start date      : " + ths.simulationDataArray.get(0).getStartDate());
		System.out.println("Investment end date        : " + ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate());
		System.out.println("Investment time (days)     : " + daysFormat.format(Days.daysBetween(dt1, dt2).getDays()));
		System.out.println("Start Investment amount    : " + numberFormat.format(INITIAL_CAPITAL));
		System.out.println("Total amount of end balance: " + numberFormat.format(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance()));
		String roundOffPercent = Float.toString((getApproxEquivalentInterestPerYear(
				INITIAL_CAPITAL, 
				ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance(), 
				Days.daysBetween(dt1, dt2).getDays())) * 100).split("\\.")[0];
		System.out.println("Approximate Interest/year  : " + roundOffPercent + "%");
		System.out.println("======================================================");

		/*
		 * Down here is commented out, they are for finding the best combination of all factors
		 */
		
//		SimulateSET50AndTFEX_V2 ths = new SimulateSET50AndTFEX_V2();
//		Date[] dateArr = { Date.valueOf("1996-08-15"), Date.valueOf("1996-10-14"), Date.valueOf("1996-12-13"),
//				Date.valueOf("1997-02-11"), Date.valueOf("1997-04-12"), Date.valueOf("1997-06-11"), Date.valueOf("1997-08-10"),
//				Date.valueOf("1997-10-09"), Date.valueOf("1997-12-08"), Date.valueOf("1998-02-06"), Date.valueOf("1998-04-07"),
//				Date.valueOf("1998-06-06"), Date.valueOf("1998-08-05"), Date.valueOf("1998-10-04"), Date.valueOf("1998-12-03"),
//				Date.valueOf("1999-02-01"), Date.valueOf("1999-04-02"), Date.valueOf("1999-06-01"), Date.valueOf("1999-07-31"),
//				Date.valueOf("1999-09-29"), Date.valueOf("1999-11-28"), Date.valueOf("2000-01-27"), Date.valueOf("2000-03-27"),
//				Date.valueOf("2000-05-26"), Date.valueOf("2000-07-25"), Date.valueOf("2000-09-23"), Date.valueOf("2000-11-22"),
//				Date.valueOf("2001-01-21"), Date.valueOf("2001-03-22"), Date.valueOf("2001-05-21"), Date.valueOf("2001-07-20"),
//				Date.valueOf("2001-09-18"), Date.valueOf("2001-11-17"), Date.valueOf("2002-01-16"), Date.valueOf("2002-03-17"),
//				Date.valueOf("2002-05-16") };
//		
//		//Print header
//		System.out.print("PercentCut;maxTfexCutLoss;SMAV");
//		for (Date dt:dateArr){
//			System.out.print(";" + dt.toString());
//		}
//		System.out.println("");
//		
//		SimulateSET50AndTFEX_V2.WAIT_AFTER_CUT_LOSS = true;
//		
//		for (float percentCut = -0.05f; percentCut >= -0.15f; percentCut = percentCut - 0.01f) {
//			for(int smav = 8; smav <= 25; smav ++){
//				for (float maxTfexCutLoss = -5000; maxTfexCutLoss >= -30000; maxTfexCutLoss = maxTfexCutLoss - 2500) {
//					ArrayList<SimulationData> sims = new ArrayList<SimulationData>();
//					for (Date dt:dateArr){
//						SimulateSET50AndTFEX_V2.START_DATE = dt;
//						SimulateSET50AndTFEX_V2.SMAV = smav;
//						SimulateSET50AndTFEX_V2.CUT_LOSS_AMOUNT_TFEX = maxTfexCutLoss;
//						SimulateSET50AndTFEX_V2.CUT_LOSS_PERCENTAGE = percentCut;
//						ths = new SimulateSET50AndTFEX_V2();
//						if(smav == 18 && maxTfexCutLoss == -25000 && dt.equals(Date.valueOf("2002-05-16")))
//							System.out.print("");
//						ths.execute(false);
//
//						SimulationData simDat = new SimulationData();
//						simDat.setStartDate(ths.simulationDataArray.get(0).getStartDate());
//						simDat.setEndDate(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate());
//						simDat.setBalance(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance());
//						sims.add(simDat);
//					}
//					
//					System.out.print(numberFormat.format(percentCut) + "%;" + numberFormat.format(maxTfexCutLoss) + ";SMAV-" + smav);
//					for(SimulationData sim:sims){
//						System.out.print(";" + numberFormat.format(sim.getBalance()));
//					}
//					System.out.println("");
//				}
//			}
//		}

	}
	
	public void execute(boolean printDetailedSimulation){
		
		boolean isInvested = false;

		float runningBalance = INITIAL_CAPITAL;
		
		try {
			
			if(weeklyData == null
					|| (weeklyData != null && SimulateSET50AndTFEX_V2.SMAV != weeklyData.get(weeklyData.firstKey()).getSMAV())){
				weeklyData = loadMarketData(weeklyFileName);
			}
//			dailyData = loadMarketData(dailyFileName);
			
			Iterator<DateTime> itrWeekly = weeklyData.keySet().iterator();
			MarketData thisWeek = null;
			
			SimulationData simulationItem = null;
			
			if(itrWeekly.hasNext()){
				thisWeek = weeklyData.get(itrWeekly.next());
			}

			DateTime stopDate = new DateTime(START_DATE).plusYears(SIMULATION_LENGTH_IN_YEAR).plusMonths(3);
			DateTime currDate = new DateTime(thisWeek.getDate());


			//Start looping through the weekly data to find a start-point, either to short or buy SET50
			while(itrWeekly.hasNext()
					&& currDate.isBefore(stopDate.getMillis())){
				
				while(itrWeekly.hasNext()
						&& (currDate.isBefore(START_DATE.getTime()) || currDate.isEqual(START_DATE.getTime()))){
					//Loop to the start point
					thisWeek = weeklyData.get(itrWeekly.next());
					currDate = new DateTime(thisWeek.getDate());
				}
				
				if( ! isInvested ){

					if(thisWeek.getHighIndex() < thisWeek.getCloseIndexSMAV()){
						//This means the candle stick is under the SMAV line
						simulationItem = new SimulationData();
						simulationItem.setTypeOfTransaction(TransactionType.SHORT_TEFX);
						
						//Find the number of contracts
						float noOfContract = (runningBalance * CUT_LOSS_PERCENTAGE) / CUT_LOSS_AMOUNT_TFEX;
						String noOfContractStr = Float.toString(noOfContract).split("\\.")[0];
						if(Integer.parseInt(noOfContractStr) > 0){
							simulationItem.setNumberOfTFEXContract(Integer.parseInt(noOfContractStr));
						}else{
							simulationItem.setNumberOfTFEXContract(1);
						}
						isInvested = true;

					}else if(thisWeek.getLowIndex() > thisWeek.getCloseIndexSMAV()){
						//This means the candle stick is above the SMAV line
						simulationItem = new SimulationData();
						simulationItem.setTypeOfTransaction(TransactionType.BUY_SET50);
						isInvested = true;

					}else{
						//This means the candle stick is in the middle of the line, need to skip run
						thisWeek = weeklyData.get(itrWeekly.next());
						currDate = new DateTime(thisWeek.getDate());
					}
					
					if(isInvested){
						simulationItem.setStartDate(thisWeek.getDate());
						simulationItem.setStartIndex(thisWeek.getCloseIndex());
						thisWeek = weeklyData.get(itrWeekly.next());
						currDate = new DateTime(thisWeek.getDate());
					}
					
				}else{
					
					boolean initSimItem = false;
					
					if(simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)){
						//Check cutloss
						float maxGap = (thisWeek.getLowIndex() - simulationItem.getStartIndex()) / simulationItem.getStartIndex();
						
						if(simulationItem.getStartIndex() * (1 + maxGap) < simulationItem.getMaximumPointLossForPeriod()){
							simulationItem.setMaximumPointLossForPeriod(simulationItem.getStartIndex() * (1 + maxGap));
						}
						
						if(maxGap < CUT_LOSS_PERCENTAGE
								&& simulationItem.getCutLossAmount() == 0){
							//Cut loss
							simulationItem.setCutLossDate(thisWeek.getDate());
							simulationItem.setCutLossAmount(runningBalance * CUT_LOSS_PERCENTAGE);
							simulationItem.setEndDate(thisWeek.getDate());
							simulationItem.setMaximumPointLossForPeriod(CUT_LOSS_PERCENTAGE * simulationItem.getStartIndex());
							simulationItem.setBalance(runningBalance * (1 + CUT_LOSS_PERCENTAGE));
							runningBalance = runningBalance * (1 + CUT_LOSS_PERCENTAGE);
							if( ! WAIT_AFTER_CUT_LOSS)
								initSimItem = true;
						}else if(thisWeek.getHighIndex() < thisWeek.getCloseIndexSMAV()){
							//This means the candle stick is under the SMAV line
							
							if(simulationItem.getCutLossAmount() == 0){
								//If it was loss cutting, no need to put anything, just end the simulation item
								simulationItem.setEndIndex(thisWeek.getCloseIndex());
								simulationItem.setEndDate(thisWeek.getDate());
								simulationItem.setBalance(runningBalance * (1 + ((thisWeek.getCloseIndex() - simulationItem.getStartIndex())/simulationItem.getStartIndex())));
								runningBalance = runningBalance * (1 + ((thisWeek.getCloseIndex() - simulationItem.getStartIndex())/simulationItem.getStartIndex()));
							}
							initSimItem = true;
						}
						
						if(initSimItem){
							simulationDataArray.add(simulationItem);
							simulationItem = new SimulationData();
							isInvested = false;
						}else{
							
							thisWeek = weeklyData.get(itrWeekly.next());
							currDate = new DateTime(thisWeek.getDate());
							
						}
						
					}else if(simulationItem.getTypeOfTransaction().equals(TransactionType.SHORT_TEFX)){
						//Check cutloss
						float maxGap = (simulationItem.getStartIndex() - thisWeek.getHighIndex()) * TFEX_COST_PER_POINT;
						
						if(maxGap < simulationItem.getMaximumPointLossForPeriod()){
							simulationItem.setMaximumPointLossForPeriod(maxGap);
						}
						
						if(maxGap < CUT_LOSS_AMOUNT_TFEX
								&& simulationItem.getCutLossAmount() == 0){
							//Cut loss
							simulationItem.setCutLossDate(thisWeek.getDate());
							simulationItem.setCutLossAmount(CUT_LOSS_AMOUNT_TFEX * simulationItem.getNumberOfTFEXContract());
							simulationItem.setEndDate(thisWeek.getDate());
							simulationItem.setMaximumPointLossForPeriod(CUT_LOSS_AMOUNT_TFEX * simulationItem.getNumberOfTFEXContract());
							simulationItem.setBalance(runningBalance + (CUT_LOSS_AMOUNT_TFEX * simulationItem.getNumberOfTFEXContract()));
							runningBalance = runningBalance + (CUT_LOSS_AMOUNT_TFEX * simulationItem.getNumberOfTFEXContract());
							if( ! WAIT_AFTER_CUT_LOSS)
								initSimItem = true;
						}else if(thisWeek.getLowIndex() > thisWeek.getCloseIndexSMAV()){
							//This means the candle stick is under the SMAV line
							
							if(simulationItem.getCutLossAmount() == 0){
								//If it was loss cutting, no need to put anything, just end the simulation item
								simulationItem.setEndDate(thisWeek.getDate());
								simulationItem.setEndIndex(thisWeek.getCloseIndex());
								simulationItem.setBalance(runningBalance + ((simulationItem.getStartIndex() - thisWeek.getCloseIndex()) * simulationItem.getNumberOfTFEXContract() * TFEX_COST_PER_POINT));
								runningBalance = runningBalance + ((simulationItem.getStartIndex() - thisWeek.getCloseIndex()) * simulationItem.getNumberOfTFEXContract() * TFEX_COST_PER_POINT);
							}
							initSimItem = true;
						}
						
						if(initSimItem){
							simulationDataArray.add(simulationItem);
							isInvested = false;
						}else{
							
							thisWeek = weeklyData.get(itrWeekly.next());
							currDate = new DateTime(thisWeek.getDate());
							
						}
						
					}
					
				}
				
			}
			
			if(printDetailedSimulation){
				System.out.println("StartDate;StartIndex;Type;EndDate;EndIndex;NoOfTFEXContrace;MaxLoss;CutLossDate;CutLossAmt;Balance");
				for(SimulationData simulateItem:simulationDataArray){
					System.out.println(simulateItem.getStartDate() + ";" + simulateItem.getStartIndex() + ";"
							+ simulateItem.getTypeOfTransaction() + ";"
							+ simulateItem.getEndDate() + ";" + simulateItem.getEndIndex() + ";" + simulateItem.getNumberOfTFEXContract() + ";"
							+ simulateItem.getMaximumPointLossForPeriod() + ";" + simulateItem.getCutLossDate()
							+  ";" + numberFormat.format(simulateItem.getCutLossAmount()) + ";" + numberFormat.format(simulateItem.getBalance()));
				}
				
				System.out.println("================ Done ================");
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private TreeMap<DateTime, MarketData> loadMarketData(String fileToLoad) throws IOException{
		
		TreeMap<DateTime, MarketData> output = new TreeMap<DateTime, MarketData>();
		
//		FileInputStream fstream = new FileInputStream(fileToLoad);
		InputStream fstream = this.getClass().getResourceAsStream("/" + fileToLoad);
		
		DataInputStream in = new DataInputStream(fstream);
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		
		/*
		 * Initialize strLine with the first line, 
		 * supposed to be hearder, so read and throw away.
		 */
		String strLine = br.readLine();
		
		while ((strLine = br.readLine()) != null) {
			
			/*
			 * To clear out all excess space characters,
			 * so that we can "split" correctly
			 */
			while(strLine.indexOf("  ") > 0){
				strLine = strLine.replaceAll("  ", " ");
			}
			
			MarketData marketData = new MarketData();
			
			String[] lineItems = strLine.split(" ");
			DateTime date = new DateTime(Integer.parseInt(getYear(lineItems[0])),
					Integer.parseInt(getMonth(lineItems[0])),
					Integer.parseInt(getDay(lineItems[0])),
					0,0);
			
//			DateTime date10Years = new DateTime(START_DATE).plusYears(SIMULATION_LENGTH_IN_YEAR).plusMonths(3);
			
//			if(date.isAfter(START_DATE.getTime())
//					&& date.isBefore(date10Years.getMillis())){
				
				marketData.setDate(new Date(date.getMillis()));
				marketData.setHighIndex(Float.parseFloat(lineItems[2].replaceAll(",","")));
				marketData.setLowIndex(Float.parseFloat(lineItems[3].replaceAll(",","")));
				marketData.setCloseIndex(Float.parseFloat(lineItems[4].replaceAll(",","")));
				marketData.setSMAV(SMAV);
				if(output.size() >= SMAV - 1){
					float sum = marketData.getCloseIndex();
					Iterator<DateTime> oItr = output.keySet().iterator();
					int i=0;
					while(oItr.hasNext()){
						i++;
						MarketData o = output.get(oItr.next());
						if(i > (output.size() - SMAV + 1)){
							sum = sum + o.getCloseIndex();
						}
					}
					marketData.setCloseIndexSMAV(sum/SMAV);
				}

				output.put(date, marketData);
			}
			
//		}
		
		br.close();
		in.close();
		fstream.close();
		
		//To make a mark of item to be clean up for those do not have SMAV for close index
		Iterator<DateTime> oItr = output.keySet().iterator();
		//This has to be defined to avoid ConcurrentModification Exception
		ArrayList<DateTime> tobeDeleted = new ArrayList<DateTime>();
		while(oItr.hasNext()){
			DateTime nextIndx = oItr.next();
			MarketData o = output.get(nextIndx);
			if(o.getCloseIndexSMAV() == 0){
				tobeDeleted.add(nextIndx);
			}
		}
		
		for(DateTime dt:tobeDeleted){
			//This is now really delete the item
			output.remove(dt);
		}
		
		return output;
		
	}
	
	/**
	 * To get 4 digits year part from the raw date from the input file.
	 * 
	 * @param rawDate - dd/mm/yy
	 * @return 4 digits year yyyy
	 */
	private String getYear(String rawDate){
		String[] tmpStr = rawDate.split("/");
		if(tmpStr.length == 3
				&& tmpStr[2].charAt(0) == '9'){
			return "19" + tmpStr[2];
		}else{
			return "20" + tmpStr[2];
		}
	}
	
	/**
	 * To get 2 digits month part from the raw date from the input file.
	 * 
	 * @param rawDate - dd/mm/yy
	 * @return 2 digits month mm
	 */
	private String getMonth(String rawDate){
		String[] tmpStr = rawDate.split("/");
		if(tmpStr.length == 3){
			return tmpStr[1];
		}else{
			return "";
		}
	}

	/**
	 * To get 2 digits day part from the raw date from the input file.
	 * 
	 * @param rawDate - dd/mm/yy
	 * @return 2 digits day dd
	 */
	private String getDay(String rawDate){
		String[] tmpStr = rawDate.split("/");
		if(tmpStr.length == 3){
			return tmpStr[0];
		}else{
			return "";
		}
	}
	
	private int getAllowedTFEXContract(float balance){
		String[] splt = Float.toString(balance * CUT_LOSS_PERCENTAGE / CUT_LOSS_AMOUNT_TFEX).split("\\.");
		return Integer.parseInt(splt[0]);
	}
	
	private static float getApproxEquivalentInterestPerYear(float initialCapital, float endedBalance, long days){
		
		int capPeriod = 180;
		float initInterest = 0.001f;
		float newEndedBalance = initialCapital;
		
		boolean reverseCalc = false;
		if(endedBalance < initialCapital){
			reverseCalc = true;
			float tmpFlt = initialCapital;
			initialCapital = endedBalance;
			endedBalance = tmpFlt;
		}
		
		while(Math.abs((endedBalance - newEndedBalance)/endedBalance) > 0.002){
			
			int i=1;
			newEndedBalance = initialCapital;
			do{
				newEndedBalance = newEndedBalance * (1 + (initInterest / 2));
				i = i+capPeriod;
			}while(i <= days);
			
			initInterest += 0.0001f;
		}
		
		if(reverseCalc){
			return (-1 * initInterest);
		}else{
			return initInterest;
		}
	}

}
